#include "GL/gl.h"

#include "draw_world.hpp"
#include "river.hpp"
#include "texture.hpp"

namespace hex {
river::river(const std::vector<corner_location>& locs)
  : locs_(locs)
{}

void river::draw() const
{
	const graphics::texture& t = graphics::texture::get("textures/seamlesswater.jpg");
	t.set_as_current_texture();
	std::vector<std::pair<GLfloat,GLfloat> > points;
	for(int n = 0; n != locs().size(); ++n) {
		int xpos = 3*locs()[n].hex.x() + (locs()[n].left_side ? 0 : 4);
		int ypos = 2*locs()[n].hex.y() + 1 + ((locs()[n].hex.x()%2) == 0 ? 0 : 1);
		const GLfloat translate_x = xpos*0.25;
		const GLfloat translate_y = ypos*0.5;
		points.push_back(std::make_pair(translate_x, translate_y));
	}

	//the points on the banks of the river
	std::vector<std::pair<GLfloat,GLfloat> > points_banka, points_bankb;
	std::vector<GLfloat> river_heights;

	const int DetailLevel = 4;
	for(int n = 0; n != points.size()-1; ++n) {
		const GLfloat src_x = points[n].first;
		const GLfloat src_y = points[n].second;
		GLfloat dst_x = points[n+1].first;
		GLfloat dst_y = points[n+1].second;
		const GLfloat vector_x = dst_x - src_x;
		const GLfloat vector_y = dst_y - src_y;
		const GLfloat normal_x = -vector_y/3.0;
		const GLfloat normal_y = vector_x/3.0;

		//this is the last segment of the river. Make it longer, to
		//reach into the ocean.
		if(n == points.size()-2) {
			dst_x = src_x + vector_x*2;
			dst_y = src_y + vector_y*2;
		}

		GLfloat next_normal_x = normal_x;
		GLfloat next_normal_y = normal_y;
		if(n < points.size() - 2) {
			const GLfloat dst2_x = points[n+2].first;
			const GLfloat dst2_y = points[n+2].second;
			const GLfloat vector_x = dst2_x - dst_x;
			const GLfloat vector_y = dst2_y - dst_y;
			next_normal_x = -vector_y/3.0;
			next_normal_y = vector_x/3.0;
		}

		std::pair<GLfloat,GLfloat> srca(src_x - normal_x, src_y - normal_y);
		std::pair<GLfloat,GLfloat> srcb(src_x + normal_x, src_y + normal_y);

		std::pair<GLfloat,GLfloat> dsta(dst_x - next_normal_x, dst_y - next_normal_y);
		std::pair<GLfloat,GLfloat> dstb(dst_x + next_normal_x, dst_y + next_normal_y);

		if(n == 0) {
			points_banka.push_back(srca);
			points_bankb.push_back(srcb);
			river_heights.push_back(depths_[0]);
		}

		for(int i = 0; i != DetailLevel; ++i) {
			const GLfloat proportion_dst = GLfloat(i+1)/GLfloat(DetailLevel+1);
			const GLfloat proportion_src = 1.0 - proportion_dst;
			std::pair<GLfloat,GLfloat> a(
			  srca.first*proportion_src + dsta.first*proportion_dst,
			  srca.second*proportion_src + dsta.second*proportion_dst);
			std::pair<GLfloat,GLfloat> b(
			  srcb.first*proportion_src + dstb.first*proportion_dst,
			  srcb.second*proportion_src + dstb.second*proportion_dst);
			points_banka.push_back(a);
			points_bankb.push_back(b);
			river_heights.push_back(depths_[n]*proportion_src + depths_[n+1]*proportion_dst);
		}
	};

	const GLfloat flow = renderer::current_frame()*0.002;
	GLfloat uv = flow;

	glColor4f(1.0, 1.0, 1.0, 0.5);

	assert(points_banka.size() == points_bankb.size());
	assert(points_banka.size() == river_heights.size());
	glBegin(GL_TRIANGLE_STRIP);
	glNormal3f(0.0, 0.0, 1.0);
	for(int n = 0; n != points_banka.size(); ++n) {
		const int points_left = points_banka.size() - n - 1;
		if(points_left < 2) {
			//if we're coming to the end, alpha out into nothing as the river
			//enters the ocean.
			glColor4f(1.0, 1.0, 1.0, 0.5*(GLfloat(points_left)/GLfloat(2)));
		}
		t.set_coord(0.0, uv);
		glVertex3f(points_banka[n].first, points_banka[n].second, river_heights[n]);

		t.set_coord(1.0, uv);
		glVertex3f(points_bankb[n].first, points_bankb[n].second, river_heights[n]);

		uv -= 0.2;
	}
	glEnd();

	glColor4f(1.0, 1.0, 1.0, 1.0);
}

void river::do_erosion(heightmap& heights)
{
	typedef std::pair<int, int> Loc;

	//calculate the locations on the heightmap.
	std::vector<Loc> hm_locs;

	const int subdiv = 1 << heights.subdivisions();
	for(int n = 0; n != locs_.size(); ++n) {
		const int xpos = 3*subdiv*locs_[n].hex.x() + (locs_[n].left_side ? 0 : 4 + 4*heights.points_between());
		const int ypos = 2*subdiv*locs_[n].hex.y() + ((locs_[n].hex.x()%2) == 0 ? 0 : subdiv) + 1 + heights.points_between();
		hm_locs.push_back(std::make_pair(xpos, ypos));
	}

	//calculate the locs at the same fine-grained level of detail that
	//the heightmap is at.
	std::vector<std::pair<int, int> > sub_locs;
	for(int n = 0; n != hm_locs.size(); ++n) {
		if(n + 1 == hm_locs.size()) {
			sub_locs.push_back(hm_locs[n]);
			continue;
		}

		std::pair<int, int> loc(hm_locs[n]);
		while(loc != hm_locs[n+1]) {
			sub_locs.push_back(loc);

			if(loc.first < hm_locs[n+1].first) {
				++loc.first;
			}

			if(loc.first > hm_locs[n+1].first) {
				--loc.first;
			}

			if(loc.second < hm_locs[n+1].second) {
				++loc.second;
			}

			if(loc.second > hm_locs[n+1].second) {
				--loc.second;
			}
		}
	}

	GLfloat lowest = 10000.0;
	//Find the lowest possible point on or adjacent to the path of the river.
	//We want to make the river even lower than the lowest point.
	for(int n = 0; n != sub_locs.size(); ++n) {
		const Loc& loc = sub_locs[n];
		for(int x = loc.first-1; x <= loc.first+1; ++x) {
			for(int y = loc.second-1; y <= loc.second+1; ++y) {
				Loc adj(x, y);
				if(heights.is_on_map(adj.first, adj.second)) {
					const GLfloat height = heights.val(adj.first, adj.second);
					if(height < lowest) {
						lowest = height;
					}
				}
			}
		}

		//if this is a new point in the river which we record depths at,
		//set the depth to just below the lowest depth we've seen so far.
		if(std::find(hm_locs.begin(), hm_locs.end(), loc) != hm_locs.end()) {
			depths_.push_back(lowest - 0.0001);
		}
	}

	const GLfloat sea_level = -0.5;
	if(depths_.back() > sea_level) {
		depths_.back() = sea_level;
	}

	lowest -= 0.00001;
	int lowest_index = 0;
	for(int n = 0; n != sub_locs.size(); ++n) {
		const Loc& loc = sub_locs[n];

		//see if we should move to the next depth marker
		const int index = std::find(hm_locs.begin(), hm_locs.end(), loc) - hm_locs.begin();
		if(index != hm_locs.size()) {
			lowest_index = index;
			assert(lowest_index < depths_.size());
		}
		for(int x = loc.first-1; x <= loc.first+1; ++x) {
			for(int y = loc.second-1; y <= loc.second+1; ++y) {
				Loc adj(x, y);
				if(heights.is_on_map(adj.first, adj.second)) {
					heights.set(adj.first, adj.second, depths_[lowest_index]-0.1);
				}
			}
		}
	}
}

}
